fix(remote): close the heredoc breakout in the bootstrap script - #185
Merged
Conversation
tas50
force-pushed
the
fix/bootstrap-heredoc-breakout
branch
from
September 8, 2026 15:15
79b3814 to
ed4c885
Compare
rubyQuote keeps a node name or server URL from interpolating Ruby into client.rb, but that client.rb is then delivered to the target inside a shell heredoc, and nothing guarded that layer. A newline in either value closes the heredoc early and leaves the remainder to run as shell, through sudo: cat <<'CINC_EOF' | sudo tee '/etc/cinc/client.rb' >/dev/null node_name 'web01 CINC_EOF id > /tmp/pwned <- runs as root on the target Fixed at both layers. BootstrapCommand rejects a newline in the node name or server URL, since neither can legitimately contain one and escaping is not worth attempting. writeFileCommand independently refuses any body holding a line equal to the terminator, so the heredoc is sound whatever a future caller passes. The node name usually comes from a flag the operator typed, so this is mostly self-inflicted. The server URL does not: it comes from the credentials profile, which is exactly the kind of file that gets shared, inherited, or checked in. Signed-off-by: Tim Smith <tim@mondoo.com>
tas50
force-pushed
the
fix/bootstrap-heredoc-breakout
branch
from
September 8, 2026 17:32
ed4c885 to
0d667a2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
rubyQuotestops a node name or server URL interpolating Ruby intoclient.rb— there is already a test for that (TestClientRBDoesNotInterpolateRuby). But theclient.rbit produces is then delivered to the target inside a shell heredoc, and nothing guarded that layer.A newline in either value closes the heredoc early and leaves the remainder to run as shell, through
sudo. Actual generated script from a probe:The quoting that exists operates one layer up and cannot see this.
Severity, honestly
The node name usually comes from
--node-name, which the operator typed, so in the common case this is self-inflicted. The server URL is not: it comes from the credentials profile, which is exactly the kind of file that gets shared between colleagues, inherited from a previous setup, or checked into a repo.I would call this hardening rather than a critical hole, but the surrounding code (
rubyQuote,shellQuote, the deliberateinstall -m 0600before writing the key) shows a clear intent to be injection-safe here, and this is the one gap in that armour.The fix
Both layers, independently:
BootstrapCommandrejects a newline in the node name or server URL. Neither can legitimately contain one, so rejecting beats attempting to escape.writeFileCommandrefuses any body containing a line equal to the terminator. Callers validate their own inputs; this makes the heredoc sound regardless of what a future caller passes.The terminator is now a named constant rather than being spelled out in three places.
Tests
TestBootstrapCommandRejectsNewlinesInValues— the breakout payload via both the node name and the server URL. Fails onmain, where it returns a working exploit script.TestWriteFileCommandRejectsDelimiterInContent— the backstop, plus a check that ordinary content is still accepted.go test ./...,go vet ./..., andgofmt -l .are clean.